﻿using RabbitMQ.Client;
using RabbitMQ.Client.Exceptions;
using System.Net;

namespace Leo.Common.RabbitMQ
{
    public class RabbitConnection : IDisposable
    {
        private IModel rabbitChannel;

        private IConnection connection;

        internal readonly RabbitConfig config;

        private bool disposed;

        public RabbitConnection(RabbitConfig config)
        {
            this.config = config;
            this.rabbitChannel = null;
            this.connection = null;
        }

        /// <summary>
        /// 初始化
        /// </summary>
        /// <exception cref="Exception"></exception>
        private void Initialize()
        {
            if (this.disposed)
            {
                //throw new JaredsException("RabbitMQ连接已经主动释放啦");
                throw new Exception("RabbitMQ连接已经主动释放啦");
            }
            this.rabbitChannel = this.GetConnection().CreateModel();
        }

        /// <summary>
        /// 获取通道
        /// </summary>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public IModel GetChannel()
        {
            if (this.rabbitChannel == null || this.rabbitChannel.IsClosed)
            {
                this.Initialize();
            }
            if (this.rabbitChannel == null)
            {
                //throw new JaredsException("创建RabbitMQ信道失败");
                throw new Exception("创建RabbitMQ信道失败");
            }
            return this.rabbitChannel;
        }

        /// <summary>
        /// 获取连接
        /// </summary>
        /// <returns></returns>
        private IConnection GetConnection()
        {
            if (this.connection == null || !this.connection.IsOpen)
            {
                this.connection = this.CreateConnection();
            }
            return this.connection;
        }

        internal IModel CreateNewChannel()
        {
            return this.GetConnection().CreateModel();
        }

        /// <summary>
        /// 创建连接
        /// </summary>
        /// <returns></returns>
        public IConnection CreateConnection()
        {
            ConnectionFactory connectionFactory = new ConnectionFactory
            {
                HostName = this.config.Host,
                Port = this.config.Port,
                UserName = this.config.UserName,
                Password = this.config.Password,
                AutomaticRecoveryEnabled = true,
                TopologyRecoveryEnabled = true,
                NetworkRecoveryInterval = TimeSpan.FromSeconds(10.0),
            };

            RabbitConfig rabbitConfig = this.config;

            if (rabbitConfig.ConnectionName == null)
            {
                rabbitConfig.ConnectionName = RabbitConnection.GetProvidedName();
            }
            if (!string.IsNullOrEmpty(this.config.ConnectionName))
            {
                connectionFactory.ClientProvidedName = this.config.ConnectionName;
            }
            if (!string.IsNullOrEmpty(this.config.VirtualHost))
            {
                connectionFactory.VirtualHost = this.config.VirtualHost;
            }
            return connectionFactory.CreateConnection();
        }


        private static string GetProvidedName()
        {
            string environmentVariable = Environment.GetEnvironmentVariable("AppName");
            if (string.IsNullOrEmpty(environmentVariable))
            {
                return null;
            }
            string hostName = Dns.GetHostName();
            return environmentVariable + "-" + hostName;
        }

        /// <summary>
        /// 声明
        /// </summary>
        /// <param name="participant"></param>
        public void Declare(RabbitObjectBase participant)
        {
            RabbitQueueInfo rabbitQueueInfo = participant as RabbitQueueInfo;
            if (rabbitQueueInfo != null)
            {
                this.DeclareQueue(rabbitQueueInfo);
                return;
            }

            RabbitExchangeInfo rabbitExchangeInfo = participant as RabbitExchangeInfo;
            if (rabbitExchangeInfo != null)
            {
                this.DeclareExchange(rabbitExchangeInfo, true);
            }
        }

        /// <summary>
        /// 申明队列
        /// </summary>
        /// <param name="queue"></param>
        private void DeclareQueue(RabbitQueueInfo queue)
        {
            RabbitExchangeInfo rabbitExchangeInfo;
            string text;
            if (queue.RetryStategy.IsRetryStrategyValid(out rabbitExchangeInfo, out text))
            {
                queue.Properties.Add("retry-addr", rabbitExchangeInfo.GetPseudoUrl(text));
                queue.BindTo(queue.RetryStategy.OrignalExchange, text);
                this.DeclareRetryExchange(rabbitExchangeInfo, queue, queue.RetryStategy.OrignalExchange.Name, text);
            }
            RabbitExchangeInfo deadExchange;
            if (queue.RejectStrategy.IsDeadStrategyValid(out deadExchange))
            {
                this.DeclareDeadExchangeAndDecorateOriginalQueue(deadExchange, queue);
            }
            this.SafeDeclareQueue(queue, true);
            foreach (RabbitQueueBinder rabbitQueueBinder in queue.Binders)
            {
                this.DeclareExchange(rabbitQueueBinder.Exchange, true);
                this.BindQueue(rabbitQueueBinder);
            }
        }

        private void SafeDeclareQueue(RabbitQueueInfo queue, bool deleteWhileConflict = true)
        {
            try
            {
                this.GetChannel().QueueDeclare(queue.Name, queue.Durable, queue.Exclusive, queue.AutoDelete, queue.Properties);
            }
            catch (OperationInterruptedException ex)
            {
                Console.Write("队列[" + queue.Name + "]声明出现冲突");
                if (!queue.redeclared || !deleteWhileConflict || ex.ShutdownReason.ReplyCode != 406)
                {
                    throw;
                }
                Console.WriteLine("，删除该冲突队列重新生成");
                IModelExensions.QueueDelete(this.GetChannel(), queue.Name, false, false);
                this.SafeDeclareQueue(queue, false);
            }
        }

        public void Delete(RabbitObjectBase obj)
        {
            if (obj is RabbitQueueInfo)
            {
                IModelExensions.QueueDelete(this.GetChannel(), obj.Name, false, false);
                return;
            }
            if (obj is RabbitExchangeInfo)
            {
                IModelExensions.ExchangeDelete(this.GetChannel(), obj.Name, false);
            }
        }

        public void BindQueue(RabbitQueueBinder binder)
        {
            Dictionary<string, object> dictionary = new Dictionary<string, object>();
            this.GetChannel().QueueBind(binder.Queue.Name, binder.Exchange.Name, binder.RoutingKey, dictionary);
        }


        public void Dispose()
        {
            if (this.rabbitChannel != null && this.rabbitChannel.IsOpen)
            {
                this.rabbitChannel.Close();
            }
            if (this.connection != null && this.connection.IsOpen)
            {
                this.connection.Close();
            }
            this.disposed = true;
        }

        /// <summary>
        /// 声明交换机
        /// </summary>
        /// <param name="exchange"></param>
        /// <param name="deleteWhileConflict"></param>
        private void DeclareExchange(RabbitExchangeInfo exchange, bool deleteWhileConflict = true)
        {
            if (exchange.ExchangeType == ExchangeWorkMode.Delayed)
            {
                CollectionExtensions.TryAdd<string, object>(exchange.Properties, "x-delayed-type", ExchangeWorkMode.Direct.GetStringValue());

            }
            try
            {
                this.GetChannel().ExchangeDeclare(exchange.Name, exchange.ExchangeType.GetStringValue(), exchange.Durable, exchange.AutoDelete, exchange.Properties);
            }
            catch (OperationInterruptedException ex)
            {
                Console.Write("声明交换机【" + exchange.Name + "】出现冲突");
                if (!exchange.redeclared || !deleteWhileConflict || ex.ShutdownReason.ReplyCode != 406)
                {
                    throw;
                }
                Console.WriteLine("，删除重建");
                IModelExensions.ExchangeDelete(this.GetChannel(), exchange.Name, false);
                this.DeclareExchange(exchange, false);
            }
        }

        private void DeclareDeadExchangeAndDecorateOriginalQueue(RabbitExchangeInfo deadExchange, RabbitQueueInfo basicQueue)
        {
            CollectionExtensions.TryAdd<string, object>(basicQueue.Properties, "x-dead-letter-exchange", deadExchange.Name);
            RabbitQueueBinder preferBinder = basicQueue.PreferBinder;
            string routingKey = ((preferBinder != null) ? preferBinder.RoutingKey : null) ?? "#";
            bool temporary = basicQueue.RejectStrategy.Temporary;
            string name = temporary ? basicQueue.Name : Utils.CombineDeadExchangeOrQueueName(basicQueue.Name);
            bool exclusive = temporary;
            bool durable = !temporary && basicQueue.Durable;
            bool autoDelete = temporary || basicQueue.AutoDelete;
            RabbitQueueInfo queue = new RabbitQueueInfo(name, exclusive, durable, autoDelete).BindTo(deadExchange, routingKey);
            this.DeclareExchange(deadExchange, true);
            this.DeclareQueue(queue);
        }

        private void DeclareRetryExchange(RabbitExchangeInfo retryExchange, RabbitQueueInfo basicQueue, string workExchangeName, string retryRoutingKey)
        {
            bool temporary;
            bool flag = temporary = basicQueue.RetryStategy.Temporary;
            bool durable = !flag && basicQueue.Durable;
            bool autoDelete = flag || basicQueue.AutoDelete;
            RabbitQueueInfo rabbitQueueInfo = new RabbitQueueInfo(flag ? basicQueue.Name : Utils.CombineRetryExchangeOrQueueName(basicQueue.Name), temporary, durable, autoDelete).BindTo(retryExchange, retryRoutingKey);
            if (basicQueue.redeclared)
            {
                rabbitQueueInfo.RedeclareWhenConflict();
            }
            rabbitQueueInfo.Properties.Add("x-dead-letter-exchange", workExchangeName);
            this.DeclareExchange(retryExchange, true);
            this.DeclareQueue(rabbitQueueInfo);
        }


    }
}
